//Copyright (c) 2006-2007 Microsoft Corporation.  All rights reserved
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;

using InvocationFramework.Common;
using InvocationFramework.Common.Exceptions;
using InvocationFramework.Common.Instrumentation;
using InvocationFramework.Common.Interfaces;
using InvocationFramework.Common.Library;
using InvocationFramework.Common.Resources;

namespace InvocationFramework.Adapters.Pipeline
{
    /// <summary>
    /// Esta clase actua como Virtual Proxy (http://www.dofactory.com/Patterns/PatternProxy.aspx) sobre el adapter real configurado
    /// para dotarlo de un pipeline
    /// </summary>
    /// <author>Rodolfo Finochietti 10/11/2005 14.29</author>
    public class PipelineAdapter : IServiceAdapter
    {
        #region Private Vars

        /// <summary>
        /// En esta variable se guarda al que se llama luego de ejecura el pipeline
        /// </summary>
        private IServiceAdapter _targetAdapter;

        /// <summary>
        /// Coleccion para guardar los filtro del pipeline de entrada ordenadas de acuerdo a la configuracion
        /// </summary>
        private SortedList<int, IFilter> _inputPipeline = new SortedList<int, IFilter>( );

        /// <summary>
        /// Coleccion para guardar los filtro del pipeline de output ordenadas de acuerdo a la configuracion
        /// </summary>
        private SortedList<int, IFilter> _outputPipeline = new SortedList<int, IFilter>( );

        /// <summary>
        /// Coleccion para guardar los filtro del pipeline de excepciones ordenadas de acuerdo a la configuracion
        /// </summary>
        private SortedList<int, IFilter> _exceptionPipeline = new SortedList<int, IFilter>( );

        #endregion

        #region IServiceAdapter Members

        /// <summary>
        /// Identificador del adaptador
        /// </summary>
        public string Id
        {
            get
            {
                return _targetAdapter.Id;
            }
        }

        /// <summary>
        /// Variable privada para el manejo de la propiedad
        /// </summary>
        private bool _initialized;

        /// <summary>
        /// Indica si el adaptador esta inicializado o no
        /// </summary>
        public bool Initialized
        {
            get
            {
                return _initialized;
            }
        }

        /// <summary>
        /// Inicializa el adaptador
        /// </summary>
        /// <param name="id">Identificador del adaptador</param>
        /// <param name="configuration">Configuracion del adaptador</param>
        public void Init( string id, XmlNode configuration )
        {
            if ( !_initialized )
            {
                try
                {
                    //Controlo que los parametros no sean nulos
                    if ( String.IsNullOrEmpty( id ) )
                        throw new ArgumentNullException( "id", String.Format( CultureInfo.InvariantCulture, Texts.PipelineAdapterArgumentNull, "id" ) );

                    if ( configuration == null )
                        throw new ArgumentNullException( "configuration", String.Format( CultureInfo.InvariantCulture, Texts.PipelineAdapterArgumentNull, "configuration" ) );

                    //Leo la configuracion del adaptador que realmente utilizara el servicio
                    XmlNode targetAdapterNode = configuration.SelectSingleNode( "targetAdapter" );

                    if ( targetAdapterNode == null )
                        throw new InvocationFrameworkException( String.Format( CultureInfo.InvariantCulture, Texts.PipelineAdapterConfigurationError, "targetAdapter" ) );
                    
                    string targetAdapterType = targetAdapterNode.Attributes[ "type" ].Value;

                    //Creo el adapter con el type especificado en la configuracion
                    try
                    {
                        _targetAdapter = ReflectionHelper.CreateObject( targetAdapterType ) as IServiceAdapter;
                    }
                    catch ( Exception ex )
                    {
                        TraceHelper.Error( ex, String.Format( CultureInfo.InvariantCulture, Texts.PipelineAdapterTargetAdapterNotLoad, targetAdapterType ) );
                    }

                    //Si lo pude crear lo inicializo
                    if ( _targetAdapter == null )
                        throw new InvocationFrameworkException( Texts.PipelineAdapterTargetAdapterNotCreated );
                    else
                        _targetAdapter.Init( id, targetAdapterNode.ChildNodes[ 0 ] );

                    //Cargo los pipelines
                    LoadPipeline( configuration.SelectNodes( "inputPipeline/filter" ), PipelineType.Input, _inputPipeline );
                    LoadPipeline( configuration.SelectNodes( "outputPipeline/filter" ), PipelineType.Output, _outputPipeline );
                    LoadPipeline( configuration.SelectNodes( "exceptionPipeline/filter" ), PipelineType.Exception, _exceptionPipeline );

                    //Marco el flag de inicializado
                    _initialized = true;

                    TraceHelper.Verbose( String.Format( CultureInfo.InvariantCulture, Texts.PipelineAdapterInitTrace, targetAdapterType ) );
                }
                catch ( Exception ex )
                {
                    TraceHelper.Error( ex, Texts.PipelineAdapterInitError );
                    throw;
                }
            }
        }

        /// <summary>
        /// Permite saber si un servicio esta disponible para ser ejecutado mediante este adaptador
        /// </summary>
        /// <param name="serviceName">Nombre del servicio</param>
        /// <returns>True este adaptador puede ejecutar este servicio, False de lo contrario</returns>
        public bool IsServiceAvailable( string serviceName )
        {
            if ( !_initialized )
                throw new InvocationFrameworkException( Texts.PipelineAdapterNotInitialized );

            return _targetAdapter.IsServiceAvailable( serviceName );
        }

        /// <summary>
        /// Verifica si existe una operacion en un servicio
        /// </summary>
        /// <param name="serviceName">Nombre del servicio</param>
        /// <param name="operationName">Nombre de la operacion</param>
        /// <returns>True si la operacion existe en el servicio, de lo contrario False</returns>
        public bool IsServiceOperationAvailable( string serviceName, string operationName )
        {
            if ( !_initialized )
                throw new InvocationFrameworkException( Texts.PipelineAdapterNotInitialized );

            return _targetAdapter.IsServiceOperationAvailable( serviceName, operationName );
        }

        /// <summary>
        /// Devuelve una lista con los nombres de los servicios mas utilizados
        /// </summary>
        /// <returns>Una lista con los nombres de los servicios mas utilizados</returns>
        public string[] GetMostRequestedServices( )
        {
            if ( !_initialized )
                throw new InvocationFrameworkException( Texts.PipelineAdapterNotInitialized );

            return _targetAdapter.GetMostRequestedServices( );
        }

        /// <summary>
        /// Ejecuta la operacion que se encuentra en un determinado servicio
        /// </summary>
        /// <param name="serviceName">Nombre del servicio</param>
        /// <param name="operationName">Nombre de la operacion a ejecutar</param>
        /// <param name="context">El contexto que se desea propagar junto con al ejecucion del servicios</param>
        /// <param name="parameters">Parametros de la operacion</param>
        /// <returns>Resultado de la ejecucion de la operacion</returns>
        public object Execute( string serviceName, string operationName, ContextDictionary context, params object[] parameters )
        {
            if ( !_initialized )
                throw new InvocationFrameworkException( Texts.PipelineAdapterNotInitialized );

            //Creo un estado para los pipelines
            PipelineState state = new PipelineState( );

            //Completo las propiedades de contexto para el pipeline de entrada
            state.ExecutionInformation = new InvocationInformation( );
            state.ExecutionInformation.ServiceName = serviceName;
            state.ExecutionInformation.OperationName = operationName;
            state.ExecutionInformation.Context = context;
            state.ExecutionInformation.Parameters = parameters;
            
            //Proceso el pipeline de entrada
            state.Pipeline = PipelineType.Input;
            ProcessPipeline( state, _inputPipeline );

            try
            {
                //Solo ejecuto el servicio si el pipeline de entrada no agrego un resultado, si lo agrego 
                //asumo que no hace falta invocar al servicio(este comportamiento se agrego para poder 
                //implementar facilmente un pipeline de cache)
                if ( state.ExecutionInformation.Result == null )
                    state.ExecutionInformation.Result = _targetAdapter.Execute( state.ExecutionInformation.ServiceName, state.ExecutionInformation.OperationName, state.ExecutionInformation.Context, state.ExecutionInformation.Parameters );
            }
            catch ( Exception ex )
            {
                //Si la ejecucion genero una excepcion proceso el pipeline de excepciones
                state.ExecutionInformation.InvocationException = ex;
                state.Pipeline = PipelineType.Exception;
                ProcessPipeline( state, _exceptionPipeline );

                throw;
            }

            //Si la ejecucion  fue exitoso proceso el pipeline de entrada
            state.Pipeline = PipelineType.Output;
            ProcessPipeline( state, _outputPipeline );

            return state.ExecutionInformation.Result;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Carga un pipeline de acuerdo a su configuracion
        /// </summary>
        /// <param name="filters">Filtros a cargar</param>
        /// <param name="pipelineType">Tipo de pipeline</param>
        /// <param name="pipeline">Pipeline donde se cargaran los filtors</param>
        private static void LoadPipeline( XmlNodeList filters, PipelineType pipelineType, SortedList<int, IFilter> pipeline )
        {
            //Creo todos los filtros del pipeline
            if ( filters != null )
            {
                foreach ( XmlNode node in filters )
                {
                    if ( node.NodeType != XmlNodeType.Element )
                        continue;
                    
                    string filterType = node.Attributes[ "type" ].Value;
                    IFilter filter = null;
                    
                    //Trato de crear el filtro
                    try
                    {
                        filter = ReflectionHelper.CreateObject( filterType ) as IFilter;
                    }
                    catch ( Exception ex )
                    {
                        TraceHelper.Error( ex, String.Format( CultureInfo.InvariantCulture, Texts.PipelineAdapterFilterNotLoad, filterType ) );
                    }

                    //Si lo pude crear lo agrego al pipeline
                    if ( filter != null )
                    {
                        //Inicializo el filtro, si el filtro tiene un nodo de configuracion se lo paso
                        filter.Init( node.ChildNodes[ 0 ] );

                        int filterOrder = Convert.ToInt32( node.Attributes[ "order" ].Value, CultureInfo.InvariantCulture );

                        //Agrego el filtro al pipeline
                        pipeline.Add( filterOrder, filter );

                        TraceHelper.Verbose( String.Format( CultureInfo.InvariantCulture, Texts.PipelineAdapterFilterAddTrace, filterType, pipelineType, filterOrder ) );
                    }
                }
            }
        }
        
        /// <summary>
        /// Procesa un pipeline
        /// </summary>
        /// <param name="state">Estado del pipeline</param>
        /// <param name="pipeline">Pipeline a procesar</param>
        private static void ProcessPipeline( PipelineState state, SortedList<int, IFilter> pipeline )
        {
            //Recorro los filtros en el orden que fueron configurados
            foreach ( KeyValuePair<int, IFilter> kvp in pipeline )
            {
                //Proceso el filtro, y si este indico un stop termino el procesamiento del pipeline
                if ( kvp.Value.Process( state ) == FilterResult.Stop )
                    break;
            }
        }

        #endregion
    }
}